package gameTheory;

import graphs.Graph;

import java.util.ArrayList;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;

public class PayoffMatrix {
	private Payoff[][] payoff;
	private Map<Strategy,Integer> othersIndx;
	private Map<Strategy,Integer> playerIndx;
	public PayoffMatrix(ProfileGenerator gen, Graph g){
		this.setPlayerStrategies(gen.player);
		this.setOtherStrategies(gen.others);
		for (Profile profile : gen.profiles) {
			this.addPayoff(profile, g);
		}

		assert this.isComplete():"Matrix should be filled by now.";
	}

	public void setPlayerStrategies(Set<Strategy> strategies){
		playerIndx = new HashMap<Strategy,Integer>();
		createIndex(playerIndx,strategies);
		if(othersIndx!=null)
			this.payoff = new Payoff[playerIndx.size()][othersIndx.size()];
	}

	public void setOtherStrategies(Set<Strategy> strategies){
		othersIndx = new HashMap<Strategy,Integer>();
		createIndex(othersIndx, strategies);
		if(playerIndx!=null)
			this.payoff = new Payoff[playerIndx.size()][othersIndx.size()];
	}

	private void createIndex(Map<Strategy,Integer> index, Set<Strategy> strategies) {
		Integer i = 0;
		for (Strategy s : strategies) {
			index.put(s, i);
			i++;
		}
	}

	public void addPayoff(Profile profile, Graph g) {
		addPayoff(profile.getPlayerA(), profile.getOthers(), profile.getPayoff(g));
	}

	public void addPayoff(Strategy player, Strategy other,Payoff p){
		Integer playerIndex = playerIndx.get(player);
		Integer othersIndex = othersIndx.get(other);
		assert playerIndex != null : "Strategy for player not found";
		assert othersIndx != null : "Strategy for others not found";

		assert (this.payoff [playerIndex][othersIndex] == null) : "Payoff already defined?";

		this.payoff [playerIndex][othersIndex] = p;
	}

	public boolean isComplete(){
		for(int x = 0 ; x < this.payoff.length ; x++) {
			for(int y = 0; y < this.payoff[0].length ; y++){
				if(payoff[x][y] == null)
					return false;
			}
		}
		return true;
	}

	public String toString(){
		StringBuilder b = new StringBuilder();
		for(int i = 0 ; i < playerIndx.size() ; i++){
			for(int j = 0 ; j < othersIndx.size() ; j++){
				b.append(this.payoff[i][j]);
				b.append(";");
			}
			int index = b.lastIndexOf(";");
			b.replace(index,index+1,"\n");
		}
		return b.toString();
	}

	public class Position{
		public int x,y;
		public Position(int x, int y){
			this.x = x; this.y = y;
		}
		public String toString(){
			return "x:" + x + " y:" + y;
		}
	}

	public Set<Position> getEquilibria(){
		Set<Position> equilibria = new HashSet<Position>();
		for(int i = 0 ; i < playerIndx.size() ; i++){
			for(int j = 0 ; j < othersIndx.size() ; j++){
				if(isBestForPlayer(i,j) && isBestForOther(i,j))
					equilibria.add(new Position(i,j));
			}
		}
		return equilibria;
	}

	private boolean isBestForPlayer(int i, int j) {
		double p = payoff[i][j].player;
		for(int x = 0 ; x < playerIndx.size() ; x++){
			if(payoff[x][j].player - p > 0)
				return false;
		}
		return true;
	}

	private boolean isBestForOther(int i, int j) {
		double p = payoff[i][j].others;
		for(int y = 0 ; y < othersIndx.size() ; y++){
			if(payoff[i][y].others - p > 0)
				return false;
		}
		return true;
	}

	public Strategy[] getOtherStrategy(){
		Strategy[] others = new Strategy[othersIndx.size()];
		for(Strategy s : othersIndx.keySet())
			others[othersIndx.get(s)] = s;
		return others;
	}

	public Strategy[] getPlayerStrategy(){
		Strategy[] player = new Strategy[playerIndx.size()];
		for(Strategy s : playerIndx.keySet())
			player[playerIndx.get(s)] = s;
		return player;
	}

	public Strategy getPlayerStrategyByIndex(int index){
		for(Strategy s:playerIndx.keySet()){
			if(playerIndx.get(s)==index)
				return s;
		}
		return null;
	}
	
	public Strategy getOtherStrategyByIndex(int index){
		for(Strategy s:othersIndx.keySet()){
			if(othersIndx.get(s)==index)
				return s;
		}
		return null;
	}
	
	public List<Position> getMaxMinStrategyPlayer(){
		List<Position> maxmin = new ArrayList<Position>();
		Double[] maxPlayer = new Double[playerIndx.size()];
		for(int i = 0 ; i < playerIndx.size() ; i++)
			maxPlayer[i] = getMinPlayer(i);
		double max = Double.NEGATIVE_INFINITY;
		for(int i = 0 ; i < maxPlayer.length ; i++)
			max = Math.max(max, maxPlayer[i]);
		for(int i = 0 ; i < maxPlayer.length ; i++){
			if(maxPlayer[i] == max)
				maxmin.add(new Position(i, -1));
		}
		return maxmin;
	}

	private double getMinPlayer(int i) {
		double cur = Double.POSITIVE_INFINITY;
		for(int j = 0 ; j < othersIndx.size() ; j++)
			cur = Math.min(cur, payoff[i][j].player);
		return cur;
	}
}
